package avicit.bdp.dgs.qa.service;

import avicit.bdp.common.base.BaseService;
import avicit.bdp.common.utils.BdpLogUtil;
import avicit.bdp.core.constant.Constants;
import avicit.bdp.dgs.qa.dao.AnalysisDAO;
import avicit.bdp.dgs.qa.dto.AnalysisDTO;
import avicit.bdp.dgs.qa.dto.QaDataSourceDTO;
import avicit.bdp.dgs.qa.utils.constant.QaConstant;
import avicit.bdp.dgs.qa.utils.enums.AnalysisType;
import avicit.bdp.dgs.qa.utils.tools.DateUtils;
import avicit.platform6.api.system.ConvertColumnClient;
import avicit.platform6.api.system.impl.SystemConstant;
import avicit.platform6.commons.utils.BusinessUtil;
import avicit.platform6.commons.utils.ComUtil;
import avicit.platform6.core.context.ThreadContextHelper;
import avicit.platform6.core.exception.BusinessException;
import avicit.platform6.core.properties.PlatformConstant;
import avicit.platform6.core.rest.msg.QueryRespBean;

import com.alibaba.fastjson2.JSONObject;
import com.github.pagehelper.Page;
import com.google.common.base.Strings;
import org.apache.commons.codec.CharEncoding;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xddf.usermodel.chart.*;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTDispBlanksAs;
import org.openxmlformats.schemas.drawingml.x2006.chart.STDispBlanksAs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @金航数码科技有限责任公司
 * @作者：developer
 * @邮箱：developer@avic-digital.com
 * @创建时间： 2023-09-07
 * @类说明：AnalysisService
 * @修改记录：
 * @注意事项：
 * @主要功能：质量分析Service
 */
@Service
public class AnalysisService extends BaseService<AnalysisDAO, AnalysisDTO> {
    private static final Logger logger = LoggerFactory.getLogger(AnalysisService.class);

    private static final String DAILY_REPORT_TMEPLATE_NAME = "数据质量日报模板V1.0.docx";

    private static final String MONTHLY_REPORT_TMEPLATE_NAME = "数据质量月报模板V1.0.docx";

    @Autowired
    private ConvertColumnClient convertColumnClient;

    @Autowired
    private MetricService metricService;

    @Autowired
    private JobReportService jobReportService;

    @Autowired
    private QaDataSourceService qaDataSourceService;

    /**
     * 新增质量分析
     *
     * @param name
     * @param selectedTime
     * @param type
     * @return
     */
    public String addAnalysis(String name, String selectedTime, Integer type, String secretLevel) {
        // 合法性检查
        checkAddValid(name, selectedTime, type);

        // 生成分析报告
        AnalysisDTO analysis = null;
        AnalysisType analysisType = AnalysisType.getTypeByCode(type);
        switch (analysisType) {
            case DAILY:
                analysis = generateDailyReport(name, selectedTime, type);
                break;
            case MONTHLY:
                analysis = generateMonthlyReport(name, selectedTime, type);
                break;
            default:
                throw new BusinessException("暂不支持" + analysisType.getDesc());
        }

        // 持久化
        analysis.setSecretLevel(secretLevel);
        this.insert(analysis);
        BdpLogUtil.log4Insert(analysis);
        return analysis.getId();
    }

    /**
     * 删除/批量删除质量分析，多个id使用英文逗号隔开
     *
     * @param id
     * @return
     */
    public Integer deleteAnalysis(String id) {
        if (StringUtils.isBlank(id)) {
            return 0;
        }

        for (String analId : Arrays.asList(id.trim().split(Constants.COMMA))) {
            this.mapper.deleteAnalysisByCond(Arrays.asList(analId));
        }

        String logTitle = "删除质量日月报：【" + id + "】";
        BdpLogUtil.log("质量日月报模块", logTitle, PlatformConstant.OpType.delete);
        return 0;
    }

    /**
     * 查询质量分析详情
     *
     * @param id 质量作业ID
     * @return
     */
    public AnalysisDTO getAnalysisDetail(String id) {
        if (StringUtils.isBlank(id)) {
            return null;
        }

        AnalysisDTO dto = this.mapper.getAnalysisDetail(id);
        if (dto == null) {
            return null;
        }

        String logTitle = "查看质量日月报：【" + JSONObject.toJSONString(dto) + "】";
        BdpLogUtil.log("质量日月报模块", logTitle, PlatformConstant.OpType.select);
        // 转换
        valueConvert(Arrays.asList(dto));
        return dto;
    }

    /**
     * 分页查询质量分析
     *
     * @param keyWords
     * @param pageNo
     * @param pageSize
     * @return
     */
    public QueryRespBean<AnalysisDTO> queryAnalysis(Integer type, String keyWords, Integer pageNo, Integer pageSize) {
        QueryRespBean<AnalysisDTO> queryRespBean = new QueryRespBean<>();

        List<String> wordSecretList = ThreadContextHelper.getWordSecretList();
        com.github.pagehelper.PageHelper.startPage(pageNo, pageSize);
        Page<AnalysisDTO> queryRet = this.mapper.queryAnalysisByCond(type, keyWords, wordSecretList);
        queryRespBean.setResult(queryRet);

        valueConvert(queryRet.getResult());
        BdpLogUtil.log4Query(queryRespBean);
        return queryRespBean;
    }

    /**
     * 判读名称是否重复
     *
     * @param name
     * @return true:重复；false:不重复
     */
    public boolean isSameName(String name) {
        long count = this.mapper.selectNameCount(name);
        if (count > 0) {
            return true;
        }

        return false;
    }

    /**
     * 下载报告
     *
     * @param id 日报/月报ID
     * @return
     */
    @Transactional
    public void downloadReport(String id, HttpServletResponse response) {
        if (StringUtils.isEmpty(id)) {
            throw new BusinessException("作业ID和流程实例ID不能同时为空");
        }

        AnalysisDTO dto = this.getAnalysisDetail(id);
        if (dto == null) {
            throw new BusinessException("未找到报告信息，id：" + id);
        }

        String logTitle = "下载质量日报/月报：【" + dto.getName() + "】";
        BdpLogUtil.log("质量日月报模块", logTitle, PlatformConstant.OpType.select);
        this.writeReport(dto, response);
    }

    /**
     * 生成日报
     *
     * @param name
     * @param selectedTime
     * @param type
     * @return
     */
    private AnalysisDTO generateDailyReport(String name, String selectedTime, Integer type) {
        AnalysisDTO report = new AnalysisDTO();
        report.setId(ComUtil.getId());
        report.setName(name);
        report.setSelectedTime(selectedTime);
        report.setType(type);

        Map<String, Object> paraMap = new HashMap<>();
        report.setReportMap(paraMap);
        report.setReportJson(JSONObject.toJSONString(paraMap));

        Map<String, Object> map = new HashMap<>();
        try {
            Date day = new SimpleDateFormat("yyyy-MM-dd").parse(selectedTime);
            report.setStartTime(DateUtils.getStartTimeOfCurrentDay(day));
            report.setEndTime(DateUtils.getEndTimeOfCurrentDay(day));

            // 填充概要信息
            fillDailyReportBaseInfo(report);

            // 填充质量评分信息
            fillDailyReportScoreInfo(report);

            // 填充质量告警信息
            fillDailyReportAlertInfo(report);

            // 填充表信息
            fillDailyReportTableInfo(report);
        } catch (Exception e) {
            logger.error("生成日报失败, errMsg=", e);
        }

        System.out.println(JSONObject.toJSONString(report.getReportMap()));
        report.setReportJson(JSONObject.toJSONString(report.getReportMap()));

        return report;
    }

    /**
     * 生成月报
     *
     * @param name
     * @param selectedTime
     * @param type
     * @return
     */
    @SuppressWarnings("暂时跟日报共用一个接口，后续月报内容发生变化，再进行修改")
    private AnalysisDTO generateMonthlyReport(String name, String selectedTime, Integer type) {
        AnalysisDTO report = new AnalysisDTO();
        report.setId(ComUtil.getId());
        report.setName(name);
        report.setSelectedTime(selectedTime);
        report.setType(type);

        Map<String, Object> paraMap = new HashMap<>();
        report.setReportMap(paraMap);
        report.setReportJson(JSONObject.toJSONString(paraMap));

        Map<String, Object> map = new HashMap<>();
        try {
            Date month = new SimpleDateFormat("yyyy-MM").parse(selectedTime);
            report.setStartTime(DateUtils.getStartTimeOfCurrentMonth(month));
            report.setEndTime(DateUtils.getEndTimeOfCurrentMonth(month));

            // 填充概要信息
            fillMonthlyReportBaseInfo(report);

            // 填充质量评分信息
            fillDailyReportScoreInfo(report);

            // 填充质量告警信息
            fillDailyReportAlertInfo(report);

            // 填充表信息
            fillDailyReportTableInfo(report);
        } catch (Exception e) {
            logger.error("生成日报失败, errMsg=", e);
        }

        System.out.println(JSONObject.toJSONString(report.getReportMap()));
        report.setReportJson(JSONObject.toJSONString(report.getReportMap()));

        return report;
    }

    /**
     * 获取日报概要信息，包括以下内容：
     * 规则模板总数、规则总数、作业总数、评估次数、告警次数、新增规则模板总数、新增作业总数、新增评估次数、新增告警次数
     *
     * @param report
     */
    private void fillDailyReportBaseInfo(AnalysisDTO report) {
        // 初始化
        Map<String, Object> paraMap = report.getReportMap();
        Map<String, Object> baseInfoMap = new HashMap<>();
        paraMap.put("baseInfo", baseInfoMap);

        // 获取系统存量和增量指标
        Map<String, Object> totalMap = this.metricService.queryBasicMetrics(null, null);
        Map<String, Object> incrementMap = this.metricService.queryBasicMetrics(report.getStartTime(), report.getEndTime());

        // 填充报告日期
        baseInfoMap.put("currTime", report.getSelectedTime());

        // 填充模板信息
        baseInfoMap.put("totalRuleTemplateNum", totalMap.get("ruleTemplateNum") == null ? QaConstant.DOUBLE_HYPHEN : totalMap.get("ruleTemplateNum"));
        baseInfoMap.put("incrementRuleTemplateNum", incrementMap.get("ruleTemplateNum") == null ? QaConstant.DOUBLE_HYPHEN : incrementMap.get("ruleTemplateNum"));

        // 填充规则信息
        baseInfoMap.put("totalRuleNum", totalMap.get("ruleNum") == null ? QaConstant.DOUBLE_HYPHEN : totalMap.get("ruleNum"));
        baseInfoMap.put("incrementRuleNum", incrementMap.get("ruleNum") == null ? QaConstant.DOUBLE_HYPHEN : incrementMap.get("ruleNum"));

        // 填充作业信息
        baseInfoMap.put("totalJobNum", totalMap.get("jobNum") == null ? QaConstant.DOUBLE_HYPHEN : totalMap.get("jobNum"));
        baseInfoMap.put("incrementJobNum", incrementMap.get("jobNum") == null ? QaConstant.DOUBLE_HYPHEN : incrementMap.get("jobNum"));

        // 填充告警信息
        baseInfoMap.put("totalAlertNum", totalMap.get("alertNum") == null ? QaConstant.DOUBLE_HYPHEN : totalMap.get("alertNum"));
        baseInfoMap.put("incrementAlertNum", incrementMap.get("alertNum") == null ? QaConstant.DOUBLE_HYPHEN : incrementMap.get("alertNum"));

        // 填充评估次数信息
        baseInfoMap.put("totalExecuteJobNum", totalMap.get("executeJobNum") == null ? QaConstant.DOUBLE_HYPHEN : totalMap.get("executeJobNum"));
        baseInfoMap.put("incrementExecuteJobNum", incrementMap.get("executeJobNum") == null ? QaConstant.DOUBLE_HYPHEN : incrementMap.get("executeJobNum"));
    }

    /**
     * 填充月报基本信息，包括以下内容：
     * 1、规则模板总数、规则总数、作业总数、评估次数、告警次数、新增规则模板总数、新增作业总数、新增评估次数、新增告警次数
     * 2、评分分布图、作业状态统计图
     *
     * @param report
     */
    private void fillMonthlyReportBaseInfo(AnalysisDTO report) {
        // 填充概要信息
        fillDailyReportBaseInfo(report);

        // 填充作业状态统计信息
        fillJobStatus(report);

        // 填充评分分布信息
        fillScoreDistribution(report);
    }

    /**
     * 填充质量作业状态统计信息
     *
     * @param report
     */
    private void fillJobStatus(AnalysisDTO report) {
        Map<String, Object> paraMap = report.getReportMap();
        Map<String, Object> baseInfoMap = (Map<String, Object>) paraMap.get("baseInfo");

        List<Map<String, Object>> stateMap = this.metricService.queryJobStateDistribution(report.getStartTime(), report.getEndTime());
        baseInfoMap.put("stateMap", stateMap);
    }

    /**
     * 填充质量评分分布图信息
     *
     * @param report
     */
    private void fillScoreDistribution(AnalysisDTO report) {
        Map<String, Object> paraMap = report.getReportMap();
        Map<String, Object> baseInfoMap = (Map<String, Object>) paraMap.get("baseInfo");

        List<Map<String, Object>> distMap = this.metricService.queryScoreDistribution(report.getStartTime(), report.getEndTime());
        baseInfoMap.put("distributionMap", distMap);
    }

    /**
     * 填充评分信息
     *
     * @param report
     */
    private void fillDailyReportScoreInfo(AnalysisDTO report) {
        Map<String, Object> paraMap = report.getReportMap();
        Map<String, Object> scoreInfoMap = new HashMap<>();
        paraMap.put("scoreInfo", scoreInfoMap);

        // 前10名排名
        List<Map<String, Object>> scoreDescList = this.metricService.queryScoreRank(0, 10, report.getStartTime(), report.getEndTime());
        if (CollectionUtils.isNotEmpty(scoreDescList)) {
            scoreInfoMap.put("maxTaskName", scoreDescList.get(0).get("name"));
            scoreInfoMap.put("maxTaskScore", scoreDescList.get(0).get("value"));
            scoreInfoMap.put("scoreRankDescList", scoreDescList);
        } else {
            scoreInfoMap.put("maxTaskName", QaConstant.DOUBLE_HYPHEN);
            scoreInfoMap.put("maxTaskScore", QaConstant.DOUBLE_HYPHEN);
        }

        // 后10名排名
        List<Map<String, Object>> scoreAscList = this.metricService.queryScoreRank(1, 10, report.getStartTime(), report.getEndTime());
        if (CollectionUtils.isNotEmpty(scoreAscList)) {
            scoreInfoMap.put("minTaskName", scoreAscList.get(0).get("name"));
            scoreInfoMap.put("minTaskScore", scoreAscList.get(0).get("value"));
            scoreInfoMap.put("scoreRankAscList", scoreAscList);
        } else {
            scoreInfoMap.put("minTaskName", QaConstant.DOUBLE_HYPHEN);
            scoreInfoMap.put("minTaskScore", QaConstant.DOUBLE_HYPHEN);
        }

        // 评分趋势
        List<Map<String, Object>> scoreTrendList = this.metricService.queryScoreTrend(report.getType() == 1 ? 3 : 0, report.getStartTime(), report.getEndTime());
        if (CollectionUtils.isNotEmpty(scoreTrendList)) {
            double maxValue = 0;
            double minValue = 0;
            String maxTime = null;
            String minTime = null;
            for (Map<String, Object> item : scoreTrendList) {
                if ((Double) item.get("value") > maxValue) {
                    maxValue = (Double) item.get("value");
                    maxTime = item.get("name").toString();
                }
                if ((Double) item.get("value") < minValue) {
                    minValue = (Double) item.get("value");
                    minTime = item.get("name").toString();
                }
            }

            scoreInfoMap.put("maxScoreTime", maxTime);
            scoreInfoMap.put("minScoreTime", minTime);
            scoreInfoMap.put("scoreTrendList", scoreTrendList);
        }
    }

    /**
     * 填充质量告警
     *
     * @param report
     */
    private void fillDailyReportAlertInfo(AnalysisDTO report) {
        Map<String, Object> paraMap = report.getReportMap();
        Map<String, Object> alertInfoMap = new HashMap<>();
        paraMap.put("alertInfo", alertInfoMap);

        // 填充当前时间
        alertInfoMap.put("currTime", report.getSelectedTime());

        // 填充告警任务总数及占比
        long totalTaskNum = this.jobReportService.countJobAlertByCond(null, report.getStartTime(), report.getEndTime());
        long totalAlertTaskNum = this.jobReportService.countJobAlertByCond(0, report.getStartTime(), report.getEndTime());
        alertInfoMap.put("totalAlertTaskNum", totalAlertTaskNum);
        if (totalTaskNum == 0) {
            alertInfoMap.put("ratio", 0);
        } else {
            if (totalAlertTaskNum == 0L) {
                alertInfoMap.put("ratio", 0);
            } else {
                double ratio = ((double) totalAlertTaskNum / (double) totalTaskNum) * 100; // 前端要求返回数字，自动加上%
                alertInfoMap.put("ratio", new BigDecimal(ratio).setScale(QaConstant.DEFAULT_SCALE, BigDecimal.ROUND_HALF_UP).doubleValue());
            }
        }

        // 告警趋势
        List<Map<String, Object>> alertList = this.metricService.queryAlertTrend(report.getType() == 1 ? 3 : 0, report.getStartTime(), report.getEndTime());
        setExtrema(alertInfoMap, alertList, "maxAlertTime", "minAlertTime");
        alertInfoMap.put("alertList", CollectionUtils.isNotEmpty(alertList) ? alertList : new ArrayList<>());
    }

    /**
     * 设置极值，包括最大值最多时段、最小值最少时段
     *
     * @param configMap
     * @param dataList
     * @param maxName
     * @param minName
     */
    private void setExtrema(Map<String, Object> configMap, List<Map<String, Object>> dataList, String maxName, String minName) {
        if (CollectionUtils.isEmpty(dataList)) {
            configMap.put(maxName, QaConstant.DOUBLE_HYPHEN);
            configMap.put(minName, QaConstant.DOUBLE_HYPHEN);

            return;
        }

        // 获取最大值、最小值
        Double maxValue = new Double(0);
        Double minValue = new Double(0);
        for (Map<String, Object> item : dataList) {
            if ((Double) item.get("value") >= maxValue) {
                maxValue = (Double) item.get("value");
            }
            if ((Double) item.get("value") <= minValue) {
                minValue = (Double) item.get("value");
            }
        }

        // 获取最大值出现时段，连续时段会合并
        int curInx = 1;
        int dataNum = dataList.size();
        int prevMaxInx = 1;
        String prevMaxTime = null;
        StringBuilder maxTimeSb = new StringBuilder();
        Map<Integer, String> inxNameMap = new HashMap<>();
        for (Map<String, Object> item : dataList) {
            if (maxValue.equals(item.get("value"))) {
                if (curInx == dataNum) {
                    if (prevMaxTime == null) {
                        maxTimeSb.append(item.get("name").toString()).append("  ");
                    } else {
                        maxTimeSb.append(inxNameMap.get(prevMaxInx)).append("至").append(item.get("name").toString()).append("  ");
                    }
                    break;
                }

                if (prevMaxTime == null) {
                    prevMaxInx = curInx;
                    prevMaxTime = item.get("name").toString();
                }

                inxNameMap.put(curInx, item.get("name").toString());
            } else {
                if (prevMaxTime == null) {
                    curInx++;
                    continue;
                }

                if (curInx - 1 == prevMaxInx) {
                    maxTimeSb.append(inxNameMap.get(prevMaxInx)).append("  ");
                } else {
                    maxTimeSb.append(inxNameMap.get(prevMaxInx)).append("至").append(inxNameMap.get(curInx - 1)).append("  ");
                }

                prevMaxTime = null;
            }

            curInx++;
        }

        // 获取最小值出现时段，连续时段会合并
        curInx = 1;
        int prevMinInx = 1;
        String prevMinTime = null;
        StringBuilder minTimeSb = new StringBuilder();
        inxNameMap.clear();
        for (Map<String, Object> item : dataList) {
            if (minValue.equals(item.get("value"))) {
                if (curInx == dataNum) {
                    if (prevMinTime == null) {
                        minTimeSb.append(item.get("name").toString()).append("  ");
                    } else {
                        minTimeSb.append(inxNameMap.get(prevMinInx)).append("至").append(item.get("name").toString()).append("  ");
                    }
                    break;
                }

                if (prevMinTime == null) {
                    prevMinInx = curInx;
                    prevMinTime = item.get("name").toString();
                }

                inxNameMap.put(curInx, item.get("name").toString());
            } else {
                if (prevMinTime == null) {
                    curInx++;
                    continue;
                }

                if (curInx - 1 == prevMinInx) {
                    minTimeSb.append(inxNameMap.get(prevMinInx)).append("  ");
                } else {
                    minTimeSb.append(inxNameMap.get(prevMinInx)).append("至").append(inxNameMap.get(curInx - 1)).append("  ");
                }

                prevMinTime = null;
            }

            curInx++;
        }

        // 设置最大值时段、最小值时段
        configMap.put(maxName, maxTimeSb.toString());
        configMap.put(minName, minTimeSb.toString());
    }

    /**
     * 填充校验表/校验字段
     *
     * @param report
     */
    private void fillDailyReportTableInfo(AnalysisDTO report) {
        Map<String, Object> paraMap = report.getReportMap();
        Map<String, Object> tableInfoMap = new HashMap<>();
        paraMap.put("tableInfo", tableInfoMap);

        // 填充当前时间
        tableInfoMap.put("currTime", report.getSelectedTime());

        // 填充校验表信息
        int inx = 1;
        List<List<Object>> checkTableList = new ArrayList<>();
        List<QaDataSourceDTO> tableList = this.qaDataSourceService.getCheckTableList(report.getStartTime(), report.getEndTime());
        if (CollectionUtils.isNotEmpty(tableList)) {
            for (QaDataSourceDTO table : tableList) {
                if (table == null) {
                    continue;
                }

                List<Object> data = new ArrayList<>();
                data.add(inx++);
                data.add(table.getTableName());
                data.add(table.getDatasourceName());
                data.add(table.getDbName());
                data.add(table.getDatasourceType());

                checkTableList.add(data);
            }

            tableInfoMap.put("totalCheckTableNum", tableList.size());
            tableInfoMap.put("checkTableList", checkTableList);
        } else {
            tableInfoMap.put("totalCheckTableNum", QaConstant.DOUBLE_HYPHEN);
            tableInfoMap.put("checkTableList", new ArrayList<>());
        }
    }

    /**
     * 新建时合法性检查
     *
     * @param name
     * @param selectedTime
     * @param type
     */
    private void checkAddValid(String name, String selectedTime, Integer type) {
        if (StringUtils.isBlank(name)) {
            throw new BusinessException("报告名字为空.");
        }
        if (selectedTime == null) {
            throw new BusinessException("选定时间为空.");
        }
        if (type == null) {
            throw new BusinessException("分析类型为空.");
        } else {
            if (AnalysisType.getTypeByCode(type) == null) {
                throw new BusinessException("分析类型不合法,当前值：" + type + ",取值范围[0,1]");
            }
        }
    }

    /**
     * 通过平台API将字段值转换为名称，包括通用选择组件、通用代码
     *
     * @param dtoList
     */
    private void valueConvert(List<AnalysisDTO> dtoList) {
        if (CollectionUtils.isEmpty(dtoList)) {
            return;
        }

        // 设置创建者名称
        Map<String, Set<String>> convertFormData = new HashMap<>();
        for (AnalysisDTO dto : dtoList) {
            BusinessUtil.createConvertSet(convertFormData, SystemConstant.USER, dto.getCreatedBy());
            BusinessUtil.createConvertSet(convertFormData, Constants.BDP_DATA_SECRET_LEVEL_KEY, dto.getSecretLevel());
        }
        if (convertFormData.size() > 0) {
            //获取请求结果
            Map<String, Map<String, String>> convertResultData = convertColumnClient.replace(convertFormData);
            //循环设置Alias或Name的值
            for (AnalysisDTO dto : dtoList) {
                dto.setCreatedBy(BusinessUtil.convertFormat(convertResultData, SystemConstant.USER, dto.getCreatedBy()));
                dto.setSecretLevelName(BusinessUtil.convertFormat(
                        convertResultData, Constants.BDP_DATA_SECRET_LEVEL_KEY, dto.getSecretLevel()));
            }
        }

        for (AnalysisDTO dto : dtoList) {
            dto.setTypeName(AnalysisType.getDescByCode(dto.getType()));
            dto.setReportMap(JSONObject.parseObject(dto.getReportJson(), Map.class));
        }
    }

    /**
     * 编排报告
     *
     * @param dto
     * @param response
     */
    private void writeReport(AnalysisDTO dto, HttpServletResponse response) {
        // step1:获取报告模板
        XWPFDocument xwpfDocument = null;
        if (dto.getType() == 0) {
            xwpfDocument = getTemplateFile(DAILY_REPORT_TMEPLATE_NAME);
        } else {
            xwpfDocument = getTemplateFile(MONTHLY_REPORT_TMEPLATE_NAME);
        }

        // step2:读取并替换报告基本信息
        replaceAllVariables(xwpfDocument, dto.getReportMap());

        // step3:填充图表
        drawEcharts(dto.getType(), xwpfDocument, dto.getReportMap());

        // step4:填充表
        fillDataTable(xwpfDocument, dto.getReportMap());

        // step5:生成判读报告
        generateNewFile(dto, xwpfDocument, response);
    }

    /**
     * 获取判读报告模板
     *
     * @return
     */
    private XWPFDocument getTemplateFile(String fileName) {
        InputStream ins = this.getClass().getClassLoader().getResourceAsStream(fileName);
        if (ins == null) {
            logger.error(String.format("未获取到模板文件[%s]", fileName));
            return null;
        }

        XWPFDocument xwpfDocument = null;
        try {
            xwpfDocument = new XWPFDocument(ins);
        } catch (IOException e) {
            logger.error("读取模板文件[" + fileName + "]失败,errMsg=" + e.getMessage());
            throw new BusinessException(String.format("读取模板文件[%s]", fileName));
        }

        return xwpfDocument;
    }

    /**
     * 替换掉模板文件中所有变量
     *
     * @param xwpfDocument
     * @param paraMap
     */
    private void replaceAllVariables(XWPFDocument xwpfDocument, Map<String, Object> paraMap) {
        if (xwpfDocument == null || paraMap == null || paraMap.size() < 1) {
            return;
        }

        Map<String, Object> varMap = new HashMap<>();
        Map<String, Object> basicInfoMap = (Map<String, Object>) paraMap.get("baseInfo");
        varMap.putAll(basicInfoMap);
        Map<String, Object> scoreInfoMap = (Map<String, Object>) paraMap.get("scoreInfo");
        varMap.putAll(scoreInfoMap);
        Map<String, Object> alertInfoMap = (Map<String, Object>) paraMap.get("alertInfo");
        varMap.putAll(alertInfoMap);
        Map<String, Object> tableInfoMap = (Map<String, Object>) paraMap.get("tableInfo");
        varMap.putAll(tableInfoMap);

        Iterator<XWPFParagraph> itPara = xwpfDocument.getParagraphsIterator();
        while (itPara.hasNext()) {
            XWPFParagraph paragraph = itPara.next();
            List<XWPFRun> runs = paragraph.getRuns();
            for (XWPFRun currRun : runs) {
                // 跳过空行
                if (currRun == null) {
                    continue;
                }

                // 替换掉关键字
                String sectionItem = currRun.getText(currRun.getTextPosition());
                if (StringUtils.isNotEmpty(sectionItem)) {
                    for (String keyWord : varMap.keySet()) {
                        sectionItem = sectionItem.replace(keyWord, String.valueOf(varMap.get(keyWord)));
                    }
                }
                currRun.setText(sectionItem, 0);
            }
        }
    }

    /**
     * 填充文件模板中图表信息，例如评分趋势图、告警趋势图等
     *
     * @param reportType
     * @param xwpfDocument
     * @param paraMap
     */
    private void drawEcharts(Integer reportType, XWPFDocument xwpfDocument, Map<String, Object> paraMap) {
        //获取word中所有图表对象
        List<XWPFChart> charts = xwpfDocument.getCharts();
        if (CollectionUtils.isEmpty(charts)) {
            return;
        }

        if (reportType == 0) { // 日报
            // 设置质量排名
            setScoreRankEchart(charts.get(0), paraMap, 0);
            setScoreRankEchart(charts.get(1), paraMap, 1);

            //设置评分趋势图
            setScoreEchart(reportType, charts.get(2), paraMap);

            //设置告警趋势图
            setAlertEchart(reportType, charts.get(3), paraMap);
        } else { // 月报
            // 设置作业状态统计和评分分布图
            setJobStateEchart(charts.get(0), paraMap);
            setScoreDistribution(charts.get(1), paraMap);

            // 设置质量排名
            setScoreRankEchart(charts.get(2), paraMap, 0);
            setScoreRankEchart(charts.get(3), paraMap, 1);

            //设置评分趋势图
            setScoreEchart(reportType, charts.get(4), paraMap);

            //设置告警趋势图
            setAlertEchart(reportType, charts.get(5), paraMap);
        }
    }

    /**
     * 填充判读结果
     *
     * @param xwpfDocument
     * @param paraMap
     */
    private void fillDataTable(XWPFDocument xwpfDocument, Map<String, Object> paraMap) {
        if (xwpfDocument == null || paraMap == null || paraMap.size() < 1) {
            return;
        }

        // 获取表格数据
        List<XWPFTable> tables = xwpfDocument.getTables();
        if (CollectionUtils.isEmpty(tables)) {
            return;
        }

        // 填充表信息
        Map<String, Object> tableInfoMap = (Map<String, Object>) paraMap.get("tableInfo");
        List<List<Object>> tableList = (List<List<Object>>) tableInfoMap.get("checkTableList");
        if (CollectionUtils.isNotEmpty(tableList)) {
            fillWordTableInfo(tables.get(0), tableList);
        }
    }

    /**
     * 生成判读报告文件
     *
     * @param dto
     * @param xwpfDocument
     * @param response
     */
    private void generateNewFile(AnalysisDTO dto, XWPFDocument xwpfDocument, HttpServletResponse response) {
        OutputStream outputStream = null;

        String fileName = "数据质量报告.docx";
        if (dto.getType() == 0) {
            fileName = dto.getSelectedTime() + "-数据质量日报.docx";
        } else {
            fileName = dto.getSelectedTime() + "-数据质量月报.docx";
        }

        try {
            // step1:设置消息头
            response.setContentType("application/octet-stream");
            String downLoadfileName = URLEncoder.encode(fileName, CharEncoding.UTF_8);
            downLoadfileName = downLoadfileName.replace("+", "%20");// 解决URLEncoder.encode空格变+号问题
            response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + downLoadfileName);

            logger.info("生成的质量分析报告：[{}]...", downLoadfileName);

            // step2：返回数据流
            outputStream = response.getOutputStream();
            xwpfDocument.write(outputStream);
        } catch (Exception e) {
            throw new BusinessException(("生成数据质量评估报告失败,fileName=" + fileName + ",errMsg=" + e.getMessage()));
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (Exception e) {
                throw new BusinessException(("生成数据质量评估报告失败,errMsg=" + e.getMessage()));
            }
        }
    }

    /**
     * 填充作业状态统计
     *
     * @param chart
     * @param paraMap
     */
    private void setJobStateEchart(XWPFChart chart, Map<String, Object> paraMap) {
        Map<String, Object> scoreInfoMap = (Map<String, Object>) paraMap.get("baseInfo");
        List<Map<String, Object>> dataList = (List<Map<String, Object>>) scoreInfoMap.get("stateMap");

        try {
            // 填充数据
            XSSFWorkbook workbook = chart.getWorkbook();
            XSSFSheet sheet = workbook.getSheetAt(0);
            if (CollectionUtils.isNotEmpty(dataList)) {
                doEchart(sheet, dataList);
            }

            //系列信息
            String[] singleBarSeriesNames = {"作业状态"};
            wordExportChar(chart, "作业状态统计图", singleBarSeriesNames, sheet, 6);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 填充评分分布图
     *
     * @param chart
     * @param paraMap
     */
    private void setScoreDistribution(XWPFChart chart, Map<String, Object> paraMap) {
        Map<String, Object> scoreInfoMap = (Map<String, Object>) paraMap.get("baseInfo");
        List<Map<String, Object>> dataList = (List<Map<String, Object>>) scoreInfoMap.get("distributionMap");

        try {
            // 填充数据
            XSSFWorkbook workbook = chart.getWorkbook();
            XSSFSheet sheet = workbook.getSheetAt(0);
            if (CollectionUtils.isNotEmpty(dataList)) {
                doEchart(sheet, dataList);
            }

            //系列信息
            String[] singleBarSeriesNames = {"得分统计"};
            wordExportChar(chart, "质量评分分布图", singleBarSeriesNames, sheet, 6);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 填充评分排名
     *
     * @param chart
     * @param paraMap
     */
    private void setScoreRankEchart(XWPFChart chart, Map<String, Object> paraMap, int type) {
        String[] singleBarSeriesNames = {"质量评分"};
        String title = null;
        List<Map<String, Object>> dataList = null;
        Map<String, Object> scoreInfoMap = (Map<String, Object>) paraMap.get("scoreInfo");
        if (type == 0) {
            title = "质量评分排行榜前10名";
            dataList = (List<Map<String, Object>>) scoreInfoMap.get("scoreRankDescList");
        } else {
            title = "质量评分排行榜后10名";
            dataList = (List<Map<String, Object>>) scoreInfoMap.get("scoreRankAscList");
        }

        try {
            // 填充数据
            XSSFWorkbook workbook = chart.getWorkbook();
            XSSFSheet sheet = workbook.getSheetAt(0);
            if (CollectionUtils.isNotEmpty(dataList)) {
                doEchart(sheet, dataList);
            }

            //系列信息
            wordExportChar(chart, title, singleBarSeriesNames, sheet, 11);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 填充评分趋势图
     *
     * @param reportType
     * @param chart
     * @param paraMap
     */
    private void setScoreEchart(Integer reportType, XWPFChart chart, Map<String, Object> paraMap) {
        Map<String, Object> scoreInfoMap = (Map<String, Object>) paraMap.get("scoreInfo");
        List<Map<String, Object>> dataList = (List<Map<String, Object>>) scoreInfoMap.get("scoreTrendList");

        if (CollectionUtils.isEmpty(dataList)) {
            return;
        }

        List<String> xDataList = dataList.stream().map(item -> (String)item.get("name")).collect(Collectors.toList());
        List<Double> yDataList = dataList.stream().map(item -> {
            BigDecimal bigDecimal = (BigDecimal)item.get("value");
            return bigDecimal.doubleValue();
        }).collect(Collectors.toList());

        plotLineChart(chart, xDataList, yDataList, "评分趋势", "评分平均值");
    }

    /**
     * 填充告警趋势图
     *
     * @param reportType
     * @param chart
     * @param paraMap
     */
    private void setAlertEchart(Integer reportType, XWPFChart chart, Map<String, Object> paraMap) {
        Map<String, Object> scoreInfoMap = (Map<String, Object>) paraMap.get("alertInfo");
        List<Map<String, Object>> dataList = (List<Map<String, Object>>) scoreInfoMap.get("alertList");

        if (CollectionUtils.isEmpty(dataList)) {
            return;
        }

        List<String> xDataList = dataList.stream().map(item -> (String)item.get("name")).collect(Collectors.toList());
        List<Double> yDataList = dataList.stream().map(item -> {
            BigDecimal bigDecimal = (BigDecimal)item.get("value");
            return bigDecimal.doubleValue();
        }).collect(Collectors.toList());

        plotLineChart(chart, xDataList, yDataList, "告警趋势", "告警数平均值");
    }

    /**
     * 绘制折线图
     *
     * @param chart
     * @param xDataList
     * @param yDataList
     * @param title
     * @param yName
     */
    public static void plotLineChart(XWPFChart chart, List<String> xDataList, List<Double> yDataList, String title, String yName) {
        try {
            // 清空模板中表格数据
            chart.clear();
            // 创建X轴
            XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.TOP);
            //  创建Y轴
            XDDFValueAxis rightAxis = chart.createValueAxis(AxisPosition.RIGHT);
            // 设置Y轴名称
            rightAxis.setTitle(yName);
            XDDFCategoryDataSource countries = XDDFDataSourcesFactory.fromArray(xDataList.toArray(new String[0]));
            // 创建并设置图表类型为折线图
            XDDFChartData data = chart.createData(ChartTypes.LINE, bottomAxis, rightAxis);
            // 加载折线图数据
            XDDFNumericalDataSource<Double> area = XDDFDataSourcesFactory.fromArray(yDataList.toArray(new Double[0]));
            // 图表加载数据，折线1
            XDDFLineChartData.Series series1 = (XDDFLineChartData.Series) data.addSeries(countries, area);
            // 折线图例标题
            series1.setTitle(title, null);
            // 平滑曲线
            series1.setSmooth(true);
            // 设置标记大小
            series1.setMarkerSize((short) 2);
            // 设置空数据显示间隙
            CTDispBlanksAs disp = CTDispBlanksAs.Factory.newInstance();
            disp.setVal(STDispBlanksAs.GAP);
            chart.getCTChart().setDispBlanksAs(disp);
            data.setVaryColors(false);
            // 绘制
            chart.plot(data);
            // 设置图表图例
            XDDFChartLegend legend = chart.getOrAddLegend();
            legend.setPosition(LegendPosition.TOP);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 填充文件模板中图表数据
     *
     * @param sheet
     * @param dataList
     */
    private void doEchart(XSSFSheet sheet, List<Map<String, Object>> dataList) {
        if (CollectionUtils.isNotEmpty(dataList)) {
            int inx = 1;
            for (Map<String, Object> data : dataList) {
                sheet.getRow(inx).getCell(0).setCellValue((String) data.get("name"));

                Double value = Double.parseDouble(data.get("value").toString());
                sheet.getRow(inx).getCell(1).setCellValue(value.doubleValue());

                inx++;
            }
        }
    }

    /**
     * 根据word模板导出 针对图表（柱状图，折线图，饼图等）的处理
     *
     * @param docChart    图表对象
     * @param title       图表标题
     * @param seriesNames 系列名称数组
     * @param sheet       图形对应的sheet表
     * @param rowNum      总行数
     * @return
     */
    private XWPFChart wordExportChar(XWPFChart docChart, String title, String[] seriesNames, XSSFSheet sheet, int rowNum) {
        //获取图表数据对象
        XDDFChartData chartData = docChart.getChartSeries().get(0);

        //word图表均对应一个内置的excel，用于保存图表对应的数据
        //excel中 第一列第二行开始的数据为分类信息
        //CellRangeAddress(1, categories.size(), 0, 0) 四个参数依次为 起始行 截止行 起始列 截止列。
        //根据分类信息的范围创建分类信息的数据源
        XDDFDataSource catDataSource = XDDFDataSourcesFactory.fromStringCellRange(sheet, new CellRangeAddress(1, rowNum, 0, 0));
        //更新数据
        for (int i = 0; i < seriesNames.length; i++) {
            //excel中各系列对应的数据的范围
            //根据数据的范围创建值的数据源
            XDDFNumericalDataSource<Double> valDataSource = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(1, rowNum, 1, 1));
            //获取图表系列的数据对象
            XDDFChartData.Series series = chartData.getSeries().get(i);
            //替换系列数据对象中的分类和值
            series.replaceData(catDataSource, valDataSource);
            //修改系列数据对象中的标题
            CellReference cellReference = docChart.setSheetTitle(seriesNames[i], 1);
            series.setTitle(seriesNames[i], cellReference);
        }
        //更新图表数据对象
        docChart.plot(chartData);
        //图表整体的标题 传空值则不替换标题
        if (!Strings.isNullOrEmpty(title)) {
            docChart.setTitleText(title);
            docChart.setTitleOverlay(false);
        }

        return docChart;
    }

    /**
     * 填充Word文档中表格信息
     *
     * @param table
     * @param valueList
     */
    private void fillWordTableInfo(XWPFTable table, List<List<Object>> valueList) {
        // 跳过表头，新增行
        for (int i = 1; i <= valueList.size(); i++) {
            XWPFTableRow row = table.createRow();
        }

        List<XWPFTableRow> rowList = table.getRows();
        for (int i = 1; i <= valueList.size(); i++) {
            XWPFTableRow xwpfTableRow = rowList.get(i);
            List<XWPFTableCell> tableCells = xwpfTableRow.getTableCells();
            List<Object> values = valueList.get(i - 1);
            if (values.size() != tableCells.size()) {
                System.out.println("质量计算结果列表和word文档中列表不匹配,valueList=" + values.size() + "cellSize=" + tableCells.size());
                continue;
            }

            for (int j = 0; j < tableCells.size(); j++) {
                XWPFTableCell xwpfTableCell = tableCells.get(j);
                xwpfTableCell.setText(values.get(j).toString());
            }
        }
    }
}
